-- Copyright (c) 2024 Huawei Technologies Co., Ltd.
-- openUBMC is licensed under Mulan PSL v2.
-- You can use this software according to the terms and conditions of the Mulan PSL v2.
-- You may obtain a copy of Mulan PSL v2 at: http://license.coscl.org.cn/MulanPSL2
-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
-- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
-- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
-- See the Mulan PSL v2 for more details.

-- Description: mcu升级
local singleton = require 'mc.singleton'
local skynet = require 'skynet'
local log = require 'mc.logging'
local fw_mgmt = require 'general_hardware.client'
local parser_cfg = require 'mcu.upgrade.parser_cfg'
local MCU_ENUMS = require 'mcu.enum.mcu_enums'
local defs = require 'mcu.upgrade.defs'
local utils = require 'mc.utils'
local fructl = require 'mcu.upgrade.fructl_handler'
local context = require 'mc.context'
local cmn = require 'common'
local upgrade_service_comm = require 'mcu.upgrade.upgrade_service.upgrade_service_comm'

local upgrade_service_mcu = {}
upgrade_service_mcu.__index = upgrade_service_mcu

local mcu_active_register_list = {
    {Key = 'FirmwareId', Value = 'MCU'},
    {Key = 'FirmwareType', Value = 'MCU'},
    {Key = 'ActiveCondition', Value = 'PowerOff'},
    {Key = 'ActiveMode', Value = 'ResetAC'},
    {Key = 'ActiveStatus', Value = 'Ready'}
}

function upgrade_service_mcu.new(bus, db, mcu_collection)
    return setmetatable({
        bus = bus,
        db = db,
        is_mcu_upgrading = false,
        mcu_collection = mcu_collection,
        upgrade_list = {},
        hpm_path = "",
        cfgs = {},
        processed_cfgs = {},
        upgrade_final_ret = defs.RET.OK,
        before_upgrade_version = ""
    }, upgrade_service_mcu)
end

local function mcu_get_upgrade_before_version(system_id, upgrade_list)
    if not next(upgrade_list) or system_id == defs.ALL_SYSTEM_ID then
        -- system_id为255时，不获取版本号
        log:error('[Mcu] Can not find int cfgs')
        return ''
    end

    for _, upgrade_obj in pairs(upgrade_list) do
        if upgrade_obj:get_system_id() ~= system_id then
            goto continue
        end
        local mcu = upgrade_obj:get_ref_mcu()
        local ok, version = pcall(mcu.repeat_get_version, mcu)
        if ok and version and version ~= "" then
            return version
        end
        ::continue::
    end

    log:error("[Mcu] get before version failed")
    return ""
end

function upgrade_service_mcu:update_processed_cfg(firmware_name)
    self.processed_cfgs[firmware_name] = true
end

-- 一个hpm包多个固件场景，firmware_mgmt会遍历固件执行process流程，知道某个process流程执行成功
-- 如果某个process流程执行失败，不需要释放mcu_upg_exe实例
-- 两种场景需要释放mcu_upg_exe实例：1. 全部固件加载都失败（通过is_all_cfgs_processed接口判断）2. process执行成功finish阶段完成
function upgrade_service_mcu:is_all_cfgs_processed()
    for firmware_name, _ in pairs(self.cfgs) do
        if not self.processed_cfgs[firmware_name] then
            return false
        end
    end
    return true
end

local function mcu_update_version(self, system_id, firmware_type, upg_ret_code)
    -- 统一更新MCU版本号,最多重试10次
    cmn.skynet.fork(function ()
        local version, retry = '', 0
        for _ = 1, 10 do
            upgrade_service_comm.update_vrd_component_version(self.bus, self.mcu_collection, system_id)
            if system_id == defs.ALL_SYSTEM_ID then
                return
            end
            -- 记录升级后版本
            version = mcu_get_upgrade_before_version(system_id, self.upgrade_list)
            if version ~= '' then
                break
            end
            -- 等待10s后重新更新
            cmn.skynet.sleep(1000)
            retry = retry + 1
        end
        log:notice('[Mcu] upgrade new version is %s, retry %s', version, retry)
        -- 记录升级维护日志
        upgrade_service_comm.record_upgrade_maint_log(firmware_type, upg_ret_code, version, self.before_upgrade_version)
    end)
end

local function paraller_upgrade_fw(self, upgrade_details, parameters)
    log:notice("[Mcu] paraller upgrade mcu start")
    local ok, ret_code
    local upg_obj_info, upg_obj, upg_index
    while true do
        -- 获取当前要处理的升级对象
        upg_obj_info, upg_obj, upg_index = upgrade_service_comm.get_cur_upgrade_obj_info(upgrade_details)
        if not upg_obj_info then
            log:debug("[Mcu] dont upgrade obj info to upgrade")
            goto continue
        end
        log:debug("[Mcu] get upgrade obj info to upgrade, upgrade index:%s", upg_index)

        -- 升级加锁处理
        ok, ret_code = upg_obj:chip_lock(require 'mc.context'.new(), 1800)
        if not ok or ret_code ~= 0 then
            log:debug("[Mcu] upgrade index:%s set lock status to 1 failed, ret code:%s", upg_index, ret_code)
            -- 加锁失败，放到队尾等待
            table.insert(upgrade_details.upgrade_list, upg_obj_info)
            goto continue
        end

        -- 开始执行升级
        skynet.fork_once(function ()
            upgrade_service_comm.upgrade_one_fw(self.bus, upg_obj_info, upgrade_details)
        end)

        ::continue::
        skynet.sleep(100)
        -- progress过程进度从20%到85%
        fw_mgmt:UpdateServiceUpdateServiceUpdateUpgradeStatus(context.new(), upgrade_details.system_id,
            upgrade_details.firmware_type, 0, upgrade_details.prog_stats_i:get_current_progress(), 'Running',
            parameters)

        -- 所有升级对象处理完成
        if upgrade_service_comm.is_upgrade_obj_proc_finish(upgrade_details) then
            break
        end
    end
    log:notice("[Mcu] paraller upgrade mcu end")
end

local function serial_upgrade_fw(self, upgrade_details, parameters)
    log:notice("[Mcu] serial upgrade mcu")
    -- 更新串行升级任务进度
    upgrade_service_comm.update_serial_upgrade_progress(upgrade_details, parameters)

    for _, upg_obj_info in pairs(upgrade_details.upgrade_list) do
        upgrade_service_comm.upgrade_one_fw(self.bus, upg_obj_info, upgrade_details)
    end
end

local function mcu_upgrade_prepare(self, system_id, firmware_type, cfg_path, hpm_path)
    self.is_mcu_upgrading = true

    -- 保存升级信息
    local _ , cfgs = parser_cfg.get_cfgs(cfg_path)
    self.cfgs = cfgs
    self.hpm_path = hpm_path

    local version
    for _, cfg in pairs(self.cfgs) do
        -- 获取升级列表
        self.upgrade_list = upgrade_service_comm.get_upgrade_list(self.bus, self.mcu_collection, cfg,
            firmware_type, system_id)
        -- 获取MCU升级前版本号
        version = mcu_get_upgrade_before_version(system_id, self.upgrade_list)
        if version ~= '' then
            break
        end
    end
    -- 设置升级标志
    upgrade_service_comm.upgrade_prepare(firmware_type, self.upgrade_list)
    self.before_upgrade_version = version
    log:notice('[Mcu] the system_id(%s) old version is %s', system_id, version)

    if upgrade_service_comm.is_component_concerned(self.cfgs, firmware_type) ~= defs.RET.OK then
        self.cfgs = {}
        self.is_mcu_upgrading = false
        log:error('[Mcu] Prepare mcu upgrade failed')
        return defs.RET.ERR, version
    end

    log:notice('[Mcu] Prepare mcu upgrade finish')
    return defs.RET.OK, version
end

local function mcu_upgrade_task(self, system_id, firmware_type, file_path, parameters)
    -- 解析出升级目录和文件名
    local dir, firmware_name = parser_cfg.parse_dir(file_path)
    self:update_processed_cfg(firmware_name)

    -- 解析出文件路径对应的cfg配置文件，并获取升级列表
    self.upgrade_list = upgrade_service_comm.get_upgrade_list(self.bus, self.mcu_collection, self.cfgs[firmware_name],
        firmware_type, system_id)
    if not next(self.upgrade_list) then
        log:error('[Mcu] No fw need to upgrade during process stage')
        return defs.RET.ERR
    end

    fw_mgmt:UpdateServiceUpdateServiceUpdateUpgradeStatus(context.new(), system_id, firmware_type, 0, 20, 'Running',
        parameters)
    -- 解压固件，最大解压限制100M
    utils.secure_tar_unzip(file_path, dir, 0x6400000, 1024)

    -- 填充升级详细信息
    local upgrade_details = upgrade_service_comm.upgrade_fill_details(system_id, firmware_type, dir, self.upgrade_list)

    -- 升级处理
    if self.upgrade_list[1]:chip_lock_supported() then
        paraller_upgrade_fw(self, upgrade_details, parameters)
    else
        serial_upgrade_fw(self, upgrade_details, parameters)
    end

    local upg_ret_code = upgrade_details.upgrade_ret_code
    mcu_update_version(self, system_id, firmware_type, upg_ret_code)

    if upg_ret_code == defs.RET.OK then
        log:notice("[Mcu] upgrade process finish")
        fw_mgmt:UpdateServiceUpdateServiceUpdateUpgradeStatus(context.new(), system_id, firmware_type,
            upg_ret_code, 90, 'Running', parameters)
    end

    return upg_ret_code
end

local function clear_mcu_upgrade_resource(self)
    self.is_mcu_upgrading = false
end

local function register_mcu_active_action(bus, upgrade_list)
    mcu_active_register_list[3] = {Key = 'ActiveCondition',
        Value = fructl.is_multihost_type(bus) and 'ChassisPowerOff' or 'PowerOff'}

    for _, upg_obj in pairs(upgrade_list) do
        local active_mode = upg_obj:query_active_mode()
        -- AC生效模式下需要注册生效流程
        if active_mode == MCU_ENUMS.RESETAC then
            fw_mgmt:FirmwareActiveFirmwareActiveRegisterActiveAction(context.new(), mcu_active_register_list)
            log:notice('[Mcu] register active action successfully')
            return
        end
    end
end

function upgrade_service_mcu:on_upgrade_prepare(system_id, firmware_type, cfg_path, hpm_path, parameters)
    log:notice("[Mcu] on upgrade prepare, firmware_type:%s", firmware_type)
    if not upgrade_service_comm.upgrade_file_path_chown(cfg_path, hpm_path) then
        fw_mgmt:UpdateServiceUpdateServicePrepareReply(context.new(), system_id, firmware_type, '', defs.RET.ERR,
            parameters)
        return
    end

    if self.is_mcu_upgrading then
        log:error('[Mcu] firmware %s is upgrading', firmware_type)
        fw_mgmt:UpdateServiceUpdateServicePrepareReply(context.new(), system_id, firmware_type, '',
            defs.RET.OTHERS_UPGRADING, parameters)
        return
    end

    skynet.fork_once(function ()
        local ok, ret_code, version_str = pcall(function ()
            return mcu_upgrade_prepare(self, system_id, firmware_type, cfg_path, hpm_path)
        end)
        if not ok then
            log:error('[Mcu] call mcu_upgrade_prepare failed, err: %s', ret_code)
            ret_code = defs.RET.ERR
            version_str = ''
        end

        if ret_code ~= defs.RET.OK then
            clear_mcu_upgrade_resource(self)
            log:error('[Mcu] upgrade prepare failed, firmware_type:%s, ok:%s, ret_code: %s', firmware_type,
                tostring(ok), ret_code)
        end
        fw_mgmt:UpdateServiceUpdateServicePrepareReply(context.new(), system_id, firmware_type, version_str, ret_code,
            parameters)
    end)
end

function upgrade_service_mcu:on_upgrade_process(system_id, firmware_type, file_path, parameters)
    log:notice("[Mcu] on upgrade process, firmware_type:%s", firmware_type)
    if not next(self.cfgs) then
        if self:is_all_cfgs_processed() then
            clear_mcu_upgrade_resource(self)
        end
        log:error('[Mcu] upgrade process failed, there is no cfgs, firmware_type:%s', firmware_type)
        fw_mgmt:UpdateServiceUpdateServiceProcessReply(context.new(), system_id, firmware_type, defs.RET.ERR,
            parameters)
        return
    end

    skynet.fork(function ()
        local ok, ret_code = pcall(function ()
            return mcu_upgrade_task(self, system_id, firmware_type, file_path, parameters)
        end)
        if not ok then
            log:error('[Mcu] call mcu_upgrade_task failed, err: %s', ret_code)
            ret_code = defs.RET.ERR
        end
        self.upgrade_final_ret = ret_code

        -- 至少有一个固件升级失败，需要最终给用户返回失败
        if ret_code ~= defs.RET.OK then
            if self:is_all_cfgs_processed() then
                clear_mcu_upgrade_resource(self)
            end
            log:error('[Mcu] upgrade process failed, firmware_type:%s, ok:%s, ret_code: %s', firmware_type,
                tostring(ok), ret_code)
        end
        fw_mgmt:UpdateServiceUpdateServiceProcessReply(context.new(), system_id, firmware_type, ret_code, parameters)
    end)
end

function upgrade_service_mcu:on_upgrade_finish(system_id, firmware_type, parameters)
    if not next(self.cfgs) then
        return
    end

    skynet.fork(function ()
        local ok, err = pcall(function ()
            upgrade_service_comm.upgrade_finish(self.upgrade_list)
        end)
        if not ok then
            clear_mcu_upgrade_resource(self)
            log:error('[Mcu] upgrade finish failed, err: %s', err)
            fw_mgmt:UpdateServiceUpdateServiceFinishReply(context.new(), system_id, firmware_type, defs.RET.ERR,
                parameters)
            return
        end

        -- 上电升级MCU注册生效策略
        ok, err = pcall(function ()
            register_mcu_active_action(self.bus, self.upgrade_list)
        end)
        if not ok then
            log:notice('[Mcu] try to register active action failed, err: %s', err)
        end
        fw_mgmt:UpdateServiceUpdateServiceFinishReply(context.new(), system_id, firmware_type,
            self.upgrade_final_ret, parameters)
        clear_mcu_upgrade_resource(self)
        log:notice("[Mcu] upgrade finish end")
    end)
end

function upgrade_service_mcu:on_active_process(system_id, firmware_type)
    log:notice('[Mcu] mcu not active process')
end

function upgrade_service_mcu:is_firmware_upgrading()
    log:debug('[Mcu] is_mcu_upgrading = %s', self.is_mcu_upgrading)
    if self.is_mcu_upgrading then
        return true
    end
    return false
end

return singleton(upgrade_service_mcu)